{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "id": "ZejZZonxwCMH",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MONAI version: 1.2.0\n",
      "Numpy version: 1.24.3\n",
      "Pytorch version: 2.0.0+cu118\n",
      "MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False\n",
      "MONAI rev id: c33f1ba588ee00229a309000e888f9817b4f1934\n",
      "MONAI __file__: /home/yawei/anaconda3/lib/python3.10/site-packages/monai/__init__.py\n",
      "\n",
      "Optional dependencies:\n",
      "Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.\n",
      "ITK version: 5.3.0\n",
      "Nibabel version: 5.1.0\n",
      "scikit-image version: 0.19.3\n",
      "Pillow version: 9.4.0\n",
      "Tensorboard version: 2.15.0a20230914\n",
      "gdown version: NOT INSTALLED or UNKNOWN VERSION.\n",
      "TorchVision version: 0.15.1+cu118\n",
      "tqdm version: 4.64.1\n",
      "lmdb version: NOT INSTALLED or UNKNOWN VERSION.\n",
      "psutil version: 5.9.0\n",
      "pandas version: 1.5.3\n",
      "einops version: 0.7.0\n",
      "transformers version: 4.36.2\n",
      "mlflow version: NOT INSTALLED or UNKNOWN VERSION.\n",
      "pynrrd version: 1.0.0\n",
      "\n",
      "For details about installing the optional dependencies, please visit:\n",
      "    https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from monai.utils import first, set_determinism\n",
    "from monai.transforms import (\n",
    "    AsDiscrete,\n",
    "    AsDiscreted,\n",
    "    EnsureChannelFirstd,\n",
    "    Compose,\n",
    "    CropForegroundd,\n",
    "    LoadImaged,\n",
    "    Orientationd,\n",
    "    RandCropByPosNegLabeld,\n",
    "    ScaleIntensityRanged,\n",
    "    Spacingd,\n",
    "    Invertd,\n",
    ")\n",
    "from monai.handlers.utils import from_engine\n",
    "# https://docs.monai.io/en/stable/networks.html#nets\n",
    "from monai.networks.nets import UNet,AttentionUnet, DynUNet, SegResNet, VNet, SegResNetVAE, UNETR\n",
    "from monai.networks.layers import Norm\n",
    "from monai.metrics import DiceMetric\n",
    "from monai.losses import DiceLoss\n",
    "from monai.inferers import sliding_window_inference\n",
    "from monai.data import CacheDataset, DataLoader, Dataset, decollate_batch\n",
    "from monai.config import print_config\n",
    "from monai.apps import download_and_extract\n",
    "import aim\n",
    "from aim.pytorch import track_gradients_dists, track_params_dists\n",
    "import torch\n",
    "import matplotlib.pyplot as plt\n",
    "import tempfile\n",
    "import shutil\n",
    "import os\n",
    "import glob\n",
    "\n",
    "print_config()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "w3EPRPqBwCMN"
   },
   "source": [
    "## Set MSD dataset path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "id": "lVZr7-kBwCMO"
   },
   "outputs": [],
   "source": [
    "root_dir = '/mnt/datawow/lyl/models/nnUNet-master/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data'\n",
    "data_dir = '/mnt/datawow/lyl/models/nnUNet-master/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task01_BrainTumour/'\n",
    "train_images = sorted(glob.glob(os.path.join(data_dir, \"imagesTr\", \"*.nii.gz\")))\n",
    "train_labels = sorted(glob.glob(os.path.join(data_dir, \"labelsTr\", \"*.nii.gz\")))\n",
    "data_dicts = [{\"image\": image_name, \"label\": label_name} for image_name, label_name in zip(train_images, train_labels)]\n",
    "train_num = int(len(data_dicts)*0.7)\n",
    "train_files, val_files = data_dicts[:-train_num], data_dicts[-train_num:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Q1Wi6EtAwCMO"
   },
   "source": [
    "## Set deterministic training for reproducibility"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "id": "dr8HRsffwCMO"
   },
   "outputs": [],
   "source": [
    "set_determinism(seed=1645)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "13ZnlKGCwCMO"
   },
   "source": [
    "## Setup transforms for training and validation\n",
    "\n",
    "Here we use several transforms to augment the dataset:\n",
    "1. `LoadImaged` loads the spleen CT images and labels from NIfTI format files.\n",
    "1. `EnsureChannelFirstd` ensures the original data to construct \"channel first\" shape.\n",
    "1. `Spacingd` adjusts the spacing by `pixdim=(1.5, 1.5, 2.)` based on the affine matrix.\n",
    "1. `Orientationd` unifies the data orientation based on the affine matrix.\n",
    "1. `ScaleIntensityRanged` extracts intensity range [-57, 164] and scales to [0, 1].\n",
    "1. `CropForegroundd` removes all zero borders to focus on the valid body area of the images and labels.\n",
    "1. `RandCropByPosNegLabeld` randomly crop patch samples from big image based on pos / neg ratio.  \n",
    "The image centers of negative samples must be in valid body area.\n",
    "1. `RandAffined` efficiently performs `rotate`, `scale`, `shear`, `translate`, etc. together based on PyTorch affine transform.\n",
    "1. `EnsureTyped` converts the numpy array to PyTorch Tensor for further steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "id": "jf7siKPOwCMO"
   },
   "outputs": [],
   "source": [
    "roi_size = (96, 96, 64)\n",
    "train_transforms = Compose(\n",
    "    [\n",
    "        LoadImaged(keys=[\"image\", \"label\"]),\n",
    "        EnsureChannelFirstd(keys=[\"image\", \"label\"]),\n",
    "        ScaleIntensityRanged(\n",
    "            keys=[\"image\"],\n",
    "            a_min=-150,\n",
    "            a_max=250,\n",
    "            b_min=0.0,\n",
    "            b_max=1.0,\n",
    "            clip=True,\n",
    "        ),\n",
    "        CropForegroundd(keys=[\"image\", \"label\"], source_key=\"image\"),\n",
    "        Orientationd(keys=[\"image\", \"label\"], axcodes=\"RAS\"),\n",
    "        Spacingd(keys=[\"image\", \"label\"], pixdim=(1.5, 1.5, 2.0), mode=(\"bilinear\", \"nearest\")),\n",
    "        RandCropByPosNegLabeld(\n",
    "            keys=[\"image\", \"label\"],\n",
    "            label_key=\"label\",\n",
    "            spatial_size=roi_size,\n",
    "            pos=1,\n",
    "            neg=1,\n",
    "            num_samples=4,\n",
    "            image_key=\"image\",\n",
    "            image_threshold=0,\n",
    "        ),\n",
    "        \n",
    "        # user can also add other random transforms\n",
    "        # RandAffined(\n",
    "        #     keys=['image', 'label'],\n",
    "        #     mode=('bilinear', 'nearest'),\n",
    "        #     prob=1.0, spatial_size=(96, 96, 96),\n",
    "        #     rotate_range=(0, 0, np.pi/15),\n",
    "        #     scale_range=(0.1, 0.1, 0.1)),\n",
    "    ]\n",
    ")\n",
    "val_transforms = Compose(\n",
    "    [\n",
    "        LoadImaged(keys=[\"image\", \"label\"]),\n",
    "        EnsureChannelFirstd(keys=[\"image\", \"label\"]),\n",
    "        ScaleIntensityRanged(\n",
    "            keys=[\"image\"],\n",
    "            a_min=-150,\n",
    "            a_max=250,\n",
    "            b_min=0.0,\n",
    "            b_max=1.0,\n",
    "            clip=True,\n",
    "        ),\n",
    "        CropForegroundd(keys=[\"image\", \"label\"], source_key=\"image\"),\n",
    "        Orientationd(keys=[\"image\", \"label\"], axcodes=\"RAS\"),\n",
    "        Spacingd(keys=[\"image\", \"label\"], pixdim=(1.5, 1.5, 2.0), mode=(\"bilinear\", \"nearest\")),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "au4rmQfDwCMP"
   },
   "source": [
    "## Check transforms in DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 408
    },
    "id": "qqcFPuVkwCMP",
    "outputId": "4189428e-4569-4453-e379-df4466208c85",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "image shape: torch.Size([160, 160, 78]), label shape: torch.Size([160, 160, 78])\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9oAAAHmCAYAAACMMPa4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy88F64QAAAACXBIWXMAAA9hAAAPYQGoP6dpAACCc0lEQVR4nOz9d5yldX3//z+v6/Q6vezszhaWLWyhrYACCgosNizEEFskGm8hP0skWPmoCXJLIJrPR80nJuRjiviVIBpjSzQRUAQVpSzN7Wybnd3Z6XN6vcrvD3Iudti+e2bOlMf9djs39lznOmde1xTe53nezXBd1xUAAAAAAKgLs9EFAAAAAAAwlxC0AQAAAACoI4I2AAAAAAB1RNAGAAAAAKCOCNoAAAAAANQRQRsAAAAAgDoiaAMAAAAAUEcEbQAAAAAA6oigDQAAAABAHRG0gRnu7rvvlmEY2rdvX6NLAQAA0+h03wPcdtttMgxDo6Ojdaul9poATg5BG5jh3vCGN+jXv/61FixY0OhSAAAAAJwEf6MLAHB8HR0d6ujoaHQZAAAAAE4SPdrADPfSYWNXXnml1q1bp1//+te69NJLFYlEtHTpUn3ta1+TJP3oRz/ShRdeqGg0qvXr1+u///u/J73erl279N73vlcrVqxQNBrVwoULdd111+m3v/3tEV97y5Yt2rhxo6LRqDo6OvTBD35QP/rRj2QYhn7+859POvfBBx/UVVddpWQyqWg0qssuu0w//elPp+R7AgDAfPTAAw/ozW9+sxYtWqRwOKyzzz5bN9100zGHiPf39+v6669XMplUU1OT3v3ud2tkZOSI8771rW/pFa94hWKxmOLxuK699lo9/fTTU305wJxG0AZmocHBQb33ve/V+9//fv3gBz/Q+vXr9b73vU+33367br31Vn3iE5/Qv//7vysej+stb3mLBgYGvOcODAyora1Nf/VXf6X//u//1t/93d/J7/frkksu0Y4dO7zzDh06pCuuuEI7duzQXXfdpf/v//v/lM1m9aEPfeiIeu655x5t3LhRyWRSX//61/Xtb39bra2tuvbaawnbAADUye7du/WKV7xCd911l+6//3792Z/9mR577DFdfvnlqlarR5z/1re+VWeffba+853v6LbbbtP3v/99XXvttZPOveOOO/SOd7xDa9as0be//W194xvfUDab1Stf+Upt3bp1Oi8PmFtcADPa1772NVeSu3fvXtd1XfeKK65wJblPPvmkd87Y2Jjr8/ncSCTiHjx40Dv+zDPPuJLc//t//+8xX9+yLLdSqbgrVqxw//RP/9Q7/vGPf9w1DMPdsmXLpPOvvfZaV5L70EMPua7ruvl83m1tbXWvu+66SefZtu2ed9557sUXX3y6lw4AwLz20vcAh3Mcx61Wq25fX58ryf3BD37gPfbnf/7nrqRJ7brruu6//uu/upLce+65x3Vd192/f7/r9/vdD3/4w5POy2azbnd3t3vDDTcc8ZoATg492sAstGDBAm3YsMG739raqs7OTp1//vnq6enxjp9zzjmSpL6+Pu+YZVm64447tGbNGgWDQfn9fgWDQT3//PPatm2bd97DDz+sdevWac2aNZO+9jve8Y5J9x999FGNj4/rxhtvlGVZ3s1xHL32ta/VE088oXw+X9frBwBgPhoeHtYf//Efq7e3V36/X4FAQEuWLJGkSW14zbve9a5J92+44Qb5/X499NBDkqSf/OQnsixL73nPeya14eFwWFdcccUR08QAnDwWQwNmodbW1iOOBYPBI44Hg0FJUqlU8o7dcsst+ru/+zt98pOf1BVXXKGWlhaZpqn3v//9KhaL3nljY2NatmzZEV+nq6tr0v2hoSFJ0tve9rZj1js+Pq5YLHYSVwYAAI7GcRxt3LhRAwMD+uxnP6v169crFovJcRy9/OUvn9SG13R3d0+67/f71dbWprGxMUkvtuEXXXTRUb+madInB5wugjYwz9xzzz16z3veozvuuGPS8dHRUTU3N3v329ravAb4cIODg5Put7e3S5L+9m//Vi9/+cuP+jVfGs4BAMCp2bx5s5599lndfffduvHGG73ju3btOuZzBgcHtXDhQu++ZVkaGxtTW1ubpBfb8O985ztezziA+iBoA/OMYRgKhUKTjv3oRz/SwYMHdfbZZ3vHrrjiCv3v//2/tXXr1knDx++7775Jz73sssvU3NysrVu3HnWhNAAAcOYMw5CkI9rw//f//t8xn/Ov//qvk6aaffvb35ZlWbryyislSddee638fr92796t3/md36l/0cA8RtAG5pk3vvGNuvvuu7V69Wqde+652rRpk/76r/9aixYtmnTezTffrH/5l3/R6173Ot1+++3q6urSvffeq+3bt0t6cThZPB7X3/7t3+rGG2/U+Pi43va2t6mzs1MjIyN69tlnNTIyorvuumvarxMAgLlk9erVWr58uT71qU/JdV21trbqP/7jP/TAAw8c8znf/e535ff7dc0112jLli367Gc/q/POO0833HCDJGnp0qW6/fbb9elPf1p79uzRa1/7WrW0tGhoaEiPP/64YrGYPve5z03XJQJzChMvgHnmb/7mb/Tud79bd955p6677jr98Ic/1He/+10tX7580nk9PT16+OGHtXLlSv3xH/+x3vWudykYDOr222+XpEnDzN/97nfroYceUi6X00033aSrr75aH/nIR/TUU0/pqquums7LAwBgTgoEAvqP//gPrVy5UjfddJPe8Y53aHh4WA8++OAxn/Pd735X27dv1/XXX68/+7M/03XXXaf777/fW8NFkm699VZ95zvf0c6dO3XjjTfq2muv1Sc+8Qn19fXpVa961XRcGjAnGa7ruo0uAsDs8Ud/9Ef65je/qbGxsUkNNQAAAIAXMHQcwDHdfvvt6unp0VlnnaVcLqf//M//1D/90z/pM5/5DCEbAAAAOAaCNoBjCgQC+uu//msdOHBAlmVpxYoV+uIXv6iPfOQjjS4NAAAAmLEYOg4AAAAAQB2xGBoAAAAAAHXU0KD993//91q2bJnC4bA2bNigX/ziF40sBwAA1BltPQBgPmpY0P7Wt76lm2++WZ/+9Kf19NNP65WvfKVe97rXaf/+/Y0qCQAA1BFtPQBgvmrYHO1LLrlEF154oe666y7v2DnnnKO3vOUtuvPOO4/7XMdxNDAwoEQiIcMwprpUAABOyHVdZbNZ9fT0yDSZmSWdWVsv0d4DAGaWU2nrG7LqeKVS0aZNm/SpT31q0vGNGzfq0UcfPeL8crmscrns3T948KDWrFkz5XUCAHCq+vv7tWjRokaX0XCn2tZLtPcAgNnhZNr6hgTt0dFR2batrq6uSce7uro0ODh4xPl33nmnPve5z01XeQAAnLZEItHoEmaEU23rpWO395fr9fIrMCV1AgBwsixV9Uv9+KTa+obuo/3SYWCu6x51aNitt96qW265xbufyWTU29s75fUBAHCqGOI82cm29dKx23u/AvIbBG0AQIP9z6Trk2nrGxK029vb5fP5jvhEe3h4+IhPviUpFAopFApNV3kAAOAMnWpbL9HeAwDmjoas1hIMBrVhwwY98MADk44/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\n",
      "text/plain": [
       "<Figure size 1200x600 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "check_ds = Dataset(data=val_files, transform=val_transforms)\n",
    "check_loader = DataLoader(check_ds, batch_size=1)\n",
    "check_data = first(check_loader)\n",
    "image, label = (check_data[\"image\"][0][0], check_data[\"label\"][0][0])\n",
    "print(f\"image shape: {image.shape}, label shape: {label.shape}\")\n",
    "# plot the slice [:, :, 80]\n",
    "\n",
    "plt.figure(\"check\", (12, 6))\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.title(\"image\")\n",
    "plt.imshow(image[:, :, 30], cmap=\"gray\")\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.title(\"label\")\n",
    "plt.imshow(label[:, :, 30])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "D0_EHJ7FwCMQ"
   },
   "source": [
    "## Define CacheDataset and DataLoader for training and validation\n",
    "\n",
    "Here we use CacheDataset to accelerate training and validation process, it's 10x faster than the regular Dataset.  \n",
    "To achieve best performance, set `cache_rate=1.0` to cache all the data, if memory is not enough, set lower value.  \n",
    "Users can also set `cache_num` instead of `cache_rate`, will use the minimum value of the 2 settings.  \n",
    "And set `num_workers` to enable multi-threads during caching.  \n",
    "If want to to try the regular Dataset, just change to use the commented code below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "kKA4gboPwCMQ",
    "outputId": "2496df99-8445-4c70-a3b1-721f9e552b34",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# train_ds = CacheDataset(data=train_files, transform=train_transforms, cache_rate=1.0, num_workers=4)\n",
    "train_ds = Dataset(data=train_files, transform=train_transforms)\n",
    "\n",
    "# use batch_size=2 to load images and use RandCropByPosNegLabeld\n",
    "# to generate 2 x 4 images for network training\n",
    "train_loader = DataLoader(train_ds, batch_size=2, shuffle=True, num_workers=4)\n",
    "\n",
    "# val_ds = CacheDataset(data=val_files, transform=val_transforms, cache_rate=1.0, num_workers=4)\n",
    "val_ds = Dataset(data=val_files, transform=val_transforms)\n",
    "val_loader = DataLoader(val_ds, batch_size=1, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "id": "d1WMn7DFKkbV"
   },
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'aim_run' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[14], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# finalize Aim Run\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43maim_run\u001b[49m\u001b[38;5;241m.\u001b[39mclose()\n",
      "\u001b[0;31mNameError\u001b[0m: name 'aim_run' is not defined"
     ]
    }
   ],
   "source": [
    "# finalize Aim Run\n",
    "aim_run.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nOgy1x1BwCMQ"
   },
   "source": [
    "## Create Model, Loss, Optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset_name = 'Task01_BrainTumor'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "id": "VM-5g2bmwCMQ"
   },
   "outputs": [],
   "source": [
    "# standard PyTorch program style: create UNet, DiceLoss and Adam optimizer\n",
    "device = torch.device(\"cuda:0\")\n",
    "\n",
    "UNet_meatdata = {\n",
    "    \"spatial_dims\": 3,\n",
    "    \"in_channels\": 4,\n",
    "    \"out_channels\": 4,\n",
    "     \"strides\": (2, 2, 2, 2),\n",
    "    \"num_res_units\": 2,\n",
    "    \"channels\":(4, 8, 16, 32, 64),\n",
    "    \"norm\": Norm.BATCH,\n",
    "}\n",
    "\n",
    "model = UNet(**UNet_meatdata).to(device)\n",
    "loss_function = DiceLoss(to_onehot_y=True, softmax=True)\n",
    "loss_type = \"DiceLoss\"\n",
    "optimizer = torch.optim.Adam(model.parameters(), 1e-4)\n",
    "dice_metric = DiceMetric(include_background=False, reduction=\"mean\")\n",
    "\n",
    "Optimizer_metadata = {}\n",
    "for ind, param_group in enumerate(optimizer.param_groups):\n",
    "    optim_meta_keys = list(param_group.keys())\n",
    "    Optimizer_metadata[f\"param_group_{ind}\"] = {\n",
    "        key: value for (key, value) in param_group.items() if \"params\" not in key\n",
    "    }\n",
    "aim_run = aim.Run()\n",
    "aim_run.name = f'{dataset_name}_{model.__class__.__name__}'\n",
    "# log model metadata\n",
    "aim_run[f\"{model.__class__.__name__}_meatdata\"] = UNet_meatdata\n",
    "# log optimizer metadata\n",
    "aim_run[\"Optimizer_metadata\"] = Optimizer_metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "VM-5g2bmwCMQ"
   },
   "outputs": [],
   "source": [
    "# standard PyTorch program style: create UNet, DiceLoss and Adam optimizer\n",
    "device = torch.device(\"cuda:0\")\n",
    "\n",
    "net_metadata = {\n",
    "    \"spatial_dims\": 3,\n",
    "    \"in_channels\": 4,\n",
    "    \"out_channels\": 2,\n",
    "    \"img_size\": (96, 96, 32),\n",
    "    \"mlp_dim\": 3072\n",
    "    #  \"strides\": (2, 2, 2, 2),\n",
    "    # \"num_res_units\": 2,\n",
    "    # \"channels\":(4, 8, 16, 32, 64),\n",
    "    # \"norm\": Norm.BATCH,\n",
    "}\n",
    "\n",
    "model = UNETR(**net_metadata).to(device)\n",
    "loss_function = DiceLoss(to_onehot_y=True, softmax=True)\n",
    "loss_type = \"DiceLoss\"\n",
    "optimizer = torch.optim.Adam(model.parameters(), 1e-4)\n",
    "dice_metric = DiceMetric(include_background=False, reduction=\"mean\")\n",
    "\n",
    "Optimizer_metadata = {}\n",
    "for ind, param_group in enumerate(optimizer.param_groups):\n",
    "    optim_meta_keys = list(param_group.keys())\n",
    "    Optimizer_metadata[f\"param_group_{ind}\"] = {\n",
    "        key: value for (key, value) in param_group.items() if \"params\" not in key\n",
    "    }\n",
    "aim_run = aim.Run()\n",
    "aim_run.name = f'{dataset_name}_{model.__class__.__name__}'\n",
    "# log model metadata\n",
    "aim_run[f\"{model.__class__.__name__}_meatdata\"] = net_metadata\n",
    "# log optimizer metadata\n",
    "aim_run[\"Optimizer_metadata\"] = Optimizer_metadata\n",
    "print(aim_run.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([8, 4, 96, 96, 64]) torch.Size([8, 1, 96, 96, 64])\n",
      "torch.Size([8, 4, 96, 96, 64]) torch.Size([8, 1, 96, 96, 64])\n",
      "torch.Size([8, 4, 96, 96, 64]) torch.Size([8, 1, 96, 96, 64])\n",
      "torch.Size([8, 4, 96, 96, 64]) torch.Size([8, 1, 96, 96, 64])\n",
      "torch.Size([8, 4, 96, 96, 64]) torch.Size([8, 1, 96, 96, 64])\n"
     ]
    }
   ],
   "source": [
    "device = torch.device(\"cuda:0\")\n",
    "\n",
    "step=0\n",
    "for batch_data in train_loader:\n",
    "    step+=1\n",
    "    inputs, labels = (\n",
    "        batch_data[\"image\"].to(device),\n",
    "        batch_data[\"label\"].to(device),\n",
    "    )\n",
    "    print(inputs.shape, labels.shape)\n",
    "    if step==5:\n",
    "        break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "np.unique(labels)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "4nD1pAY-wCMR"
   },
   "source": [
    "## Execute a typical PyTorch training process"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "id": "KayxFseYwCMR",
    "scrolled": true,
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------\n",
      "epoch 1/600\n",
      "1/73, train_loss: 0.8784\n",
      "2/73, train_loss: 0.8831\n",
      "3/73, train_loss: 0.8745\n",
      "4/73, train_loss: 0.8664\n",
      "5/73, train_loss: 0.8718\n",
      "6/73, train_loss: 0.8711\n",
      "7/73, train_loss: 0.8748\n",
      "8/73, train_loss: 0.8645\n",
      "9/73, train_loss: 0.8770\n",
      "10/73, train_loss: 0.8753\n",
      "11/73, train_loss: 0.8704\n",
      "12/73, train_loss: 0.8732\n",
      "13/73, train_loss: 0.8692\n",
      "14/73, train_loss: 0.8728\n",
      "15/73, train_loss: 0.8715\n",
      "16/73, train_loss: 0.8654\n",
      "17/73, train_loss: 0.8633\n",
      "18/73, train_loss: 0.8688\n",
      "19/73, train_loss: 0.8624\n",
      "20/73, train_loss: 0.8616\n",
      "21/73, train_loss: 0.8641\n",
      "22/73, train_loss: 0.8637\n",
      "23/73, train_loss: 0.8691\n",
      "24/73, train_loss: 0.8638\n",
      "25/73, train_loss: 0.8621\n",
      "26/73, train_loss: 0.8540\n",
      "27/73, train_loss: 0.8663\n",
      "28/73, train_loss: 0.8630\n",
      "29/73, train_loss: 0.8643\n",
      "30/73, train_loss: 0.8608\n",
      "31/73, train_loss: 0.8533\n",
      "32/73, train_loss: 0.8557\n",
      "33/73, train_loss: 0.8525\n",
      "34/73, train_loss: 0.8636\n",
      "35/73, train_loss: 0.8658\n",
      "36/73, train_loss: 0.8619\n",
      "37/73, train_loss: 0.8540\n",
      "38/73, train_loss: 0.8569\n",
      "39/73, train_loss: 0.8575\n",
      "40/73, train_loss: 0.8500\n",
      "41/73, train_loss: 0.8537\n",
      "42/73, train_loss: 0.8516\n",
      "43/73, train_loss: 0.8625\n",
      "44/73, train_loss: 0.8557\n",
      "45/73, train_loss: 0.8553\n",
      "46/73, train_loss: 0.8568\n",
      "47/73, train_loss: 0.8554\n",
      "48/73, train_loss: 0.8580\n",
      "49/73, train_loss: 0.8463\n",
      "50/73, train_loss: 0.8382\n",
      "51/73, train_loss: 0.8484\n",
      "52/73, train_loss: 0.8484\n",
      "53/73, train_loss: 0.8462\n",
      "54/73, train_loss: 0.8469\n",
      "55/73, train_loss: 0.8516\n",
      "56/73, train_loss: 0.8447\n",
      "57/73, train_loss: 0.8401\n",
      "58/73, train_loss: 0.8472\n",
      "59/73, train_loss: 0.8493\n",
      "60/73, train_loss: 0.8378\n",
      "61/73, train_loss: 0.8423\n",
      "62/73, train_loss: 0.8485\n",
      "63/73, train_loss: 0.8351\n",
      "64/73, train_loss: 0.8379\n",
      "65/73, train_loss: 0.8442\n",
      "66/73, train_loss: 0.8462\n",
      "67/73, train_loss: 0.8360\n",
      "68/73, train_loss: 0.8438\n",
      "69/73, train_loss: 0.8389\n",
      "70/73, train_loss: 0.8331\n",
      "71/73, train_loss: 0.8314\n",
      "72/73, train_loss: 0.8213\n",
      "73/73, train_loss: 0.8207\n",
      "epoch 1 average loss: 0.8561\n",
      "----------\n",
      "epoch 2/600\n",
      "1/73, train_loss: 0.8415\n",
      "2/73, train_loss: 0.8357\n",
      "3/73, train_loss: 0.8369\n",
      "4/73, train_loss: 0.8383\n",
      "5/73, train_loss: 0.8336\n",
      "6/73, train_loss: 0.8371\n",
      "7/73, train_loss: 0.8364\n",
      "8/73, train_loss: 0.8199\n",
      "9/73, train_loss: 0.8160\n",
      "10/73, train_loss: 0.8270\n",
      "11/73, train_loss: 0.8199\n",
      "12/73, train_loss: 0.8336\n",
      "13/73, train_loss: 0.8201\n",
      "14/73, train_loss: 0.8265\n",
      "15/73, train_loss: 0.8183\n",
      "16/73, train_loss: 0.8379\n",
      "17/73, train_loss: 0.8106\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[18], line 18\u001b[0m\n\u001b[1;32m     16\u001b[0m epoch_loss \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[1;32m     17\u001b[0m step \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[0;32m---> 18\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m batch_data \u001b[38;5;129;01min\u001b[39;00m train_loader:\n\u001b[1;32m     19\u001b[0m     step \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m     20\u001b[0m     inputs, labels \u001b[38;5;241m=\u001b[39m (\n\u001b[1;32m     21\u001b[0m         batch_data[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mimage\u001b[39m\u001b[38;5;124m\"\u001b[39m]\u001b[38;5;241m.\u001b[39mto(device),\n\u001b[1;32m     22\u001b[0m         batch_data[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mlabel\u001b[39m\u001b[38;5;124m\"\u001b[39m]\u001b[38;5;241m.\u001b[39mto(device),\n\u001b[1;32m     23\u001b[0m     )\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/torch/utils/data/dataloader.py:634\u001b[0m, in \u001b[0;36m_BaseDataLoaderIter.__next__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    631\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_sampler_iter \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m    632\u001b[0m     \u001b[38;5;66;03m# TODO(https://github.com/pytorch/pytorch/issues/76750)\u001b[39;00m\n\u001b[1;32m    633\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_reset()  \u001b[38;5;66;03m# type: ignore[call-arg]\u001b[39;00m\n\u001b[0;32m--> 634\u001b[0m data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_next_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    635\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_num_yielded \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m    636\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_dataset_kind \u001b[38;5;241m==\u001b[39m _DatasetKind\u001b[38;5;241m.\u001b[39mIterable \u001b[38;5;129;01mand\u001b[39;00m \\\n\u001b[1;32m    637\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_IterableDataset_len_called \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \\\n\u001b[1;32m    638\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_num_yielded \u001b[38;5;241m>\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_IterableDataset_len_called:\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/torch/utils/data/dataloader.py:1329\u001b[0m, in \u001b[0;36m_MultiProcessingDataLoaderIter._next_data\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m   1326\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_process_data(data)\n\u001b[1;32m   1328\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_shutdown \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_tasks_outstanding \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[0;32m-> 1329\u001b[0m idx, data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1330\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_tasks_outstanding \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m   1331\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_dataset_kind \u001b[38;5;241m==\u001b[39m _DatasetKind\u001b[38;5;241m.\u001b[39mIterable:\n\u001b[1;32m   1332\u001b[0m     \u001b[38;5;66;03m# Check for _IterableDatasetStopIteration\u001b[39;00m\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/torch/utils/data/dataloader.py:1295\u001b[0m, in \u001b[0;36m_MultiProcessingDataLoaderIter._get_data\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m   1291\u001b[0m     \u001b[38;5;66;03m# In this case, `self._data_queue` is a `queue.Queue`,. But we don't\u001b[39;00m\n\u001b[1;32m   1292\u001b[0m     \u001b[38;5;66;03m# need to call `.task_done()` because we don't use `.join()`.\u001b[39;00m\n\u001b[1;32m   1293\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m   1294\u001b[0m     \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1295\u001b[0m         success, data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_try_get_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1296\u001b[0m         \u001b[38;5;28;01mif\u001b[39;00m success:\n\u001b[1;32m   1297\u001b[0m             \u001b[38;5;28;01mreturn\u001b[39;00m data\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/torch/utils/data/dataloader.py:1133\u001b[0m, in \u001b[0;36m_MultiProcessingDataLoaderIter._try_get_data\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m   1120\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_try_get_data\u001b[39m(\u001b[38;5;28mself\u001b[39m, timeout\u001b[38;5;241m=\u001b[39m_utils\u001b[38;5;241m.\u001b[39mMP_STATUS_CHECK_INTERVAL):\n\u001b[1;32m   1121\u001b[0m     \u001b[38;5;66;03m# Tries to fetch data from `self._data_queue` once for a given timeout.\u001b[39;00m\n\u001b[1;32m   1122\u001b[0m     \u001b[38;5;66;03m# This can also be used as inner loop of fetching without timeout, with\u001b[39;00m\n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m   1130\u001b[0m     \u001b[38;5;66;03m# Returns a 2-tuple:\u001b[39;00m\n\u001b[1;32m   1131\u001b[0m     \u001b[38;5;66;03m#   (bool: whether successfully get data, any: data if successful else None)\u001b[39;00m\n\u001b[1;32m   1132\u001b[0m     \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 1133\u001b[0m         data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_data_queue\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1134\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m (\u001b[38;5;28;01mTrue\u001b[39;00m, data)\n\u001b[1;32m   1135\u001b[0m     \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m   1136\u001b[0m         \u001b[38;5;66;03m# At timeout and error, we manually check whether any worker has\u001b[39;00m\n\u001b[1;32m   1137\u001b[0m         \u001b[38;5;66;03m# failed. Note that this is the only mechanism for Windows to detect\u001b[39;00m\n\u001b[1;32m   1138\u001b[0m         \u001b[38;5;66;03m# worker failures.\u001b[39;00m\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/multiprocessing/queues.py:113\u001b[0m, in \u001b[0;36mQueue.get\u001b[0;34m(self, block, timeout)\u001b[0m\n\u001b[1;32m    111\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m block:\n\u001b[1;32m    112\u001b[0m     timeout \u001b[38;5;241m=\u001b[39m deadline \u001b[38;5;241m-\u001b[39m time\u001b[38;5;241m.\u001b[39mmonotonic()\n\u001b[0;32m--> 113\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_poll\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[1;32m    114\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m Empty\n\u001b[1;32m    115\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_poll():\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/multiprocessing/connection.py:257\u001b[0m, in \u001b[0;36m_ConnectionBase.poll\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m    255\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_closed()\n\u001b[1;32m    256\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_readable()\n\u001b[0;32m--> 257\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_poll\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/multiprocessing/connection.py:424\u001b[0m, in \u001b[0;36mConnection._poll\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m    423\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_poll\u001b[39m(\u001b[38;5;28mself\u001b[39m, timeout):\n\u001b[0;32m--> 424\u001b[0m     r \u001b[38;5;241m=\u001b[39m \u001b[43mwait\u001b[49m\u001b[43m(\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    425\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mbool\u001b[39m(r)\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/multiprocessing/connection.py:931\u001b[0m, in \u001b[0;36mwait\u001b[0;34m(object_list, timeout)\u001b[0m\n\u001b[1;32m    928\u001b[0m     deadline \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mmonotonic() \u001b[38;5;241m+\u001b[39m timeout\n\u001b[1;32m    930\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m--> 931\u001b[0m     ready \u001b[38;5;241m=\u001b[39m \u001b[43mselector\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mselect\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    932\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m ready:\n\u001b[1;32m    933\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m [key\u001b[38;5;241m.\u001b[39mfileobj \u001b[38;5;28;01mfor\u001b[39;00m (key, events) \u001b[38;5;129;01min\u001b[39;00m ready]\n",
      "File \u001b[0;32m~/anaconda3/lib/python3.10/selectors.py:416\u001b[0m, in \u001b[0;36m_PollLikeSelector.select\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m    414\u001b[0m ready \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m    415\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 416\u001b[0m     fd_event_list \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_selector\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpoll\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    417\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mInterruptedError\u001b[39;00m:\n\u001b[1;32m    418\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m ready\n",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "max_epochs = 600\n",
    "val_interval = 5\n",
    "best_metric = -1\n",
    "best_metric_epoch = -1\n",
    "epoch_loss_values = []\n",
    "metric_values = []\n",
    "post_pred = Compose([AsDiscrete(argmax=True, to_onehot=2)])\n",
    "post_label = Compose([AsDiscrete(to_onehot=2)])\n",
    "\n",
    "slice_to_track = 30\n",
    "\n",
    "for epoch in range(max_epochs):\n",
    "    print(\"-\" * 10)\n",
    "    print(f\"epoch {epoch + 1}/{max_epochs}\")\n",
    "    model.train()\n",
    "    epoch_loss = 0\n",
    "    step = 0\n",
    "    for batch_data in train_loader:\n",
    "        step += 1\n",
    "        inputs, labels = (\n",
    "            batch_data[\"image\"].to(device),\n",
    "            batch_data[\"label\"].to(device),\n",
    "        )\n",
    "        \n",
    "        optimizer.zero_grad()\n",
    "        outputs = model(inputs)\n",
    "        loss = loss_function(outputs, labels)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        epoch_loss += loss.item()\n",
    "        print(f\"{step}/{len(train_ds) // train_loader.batch_size}, \" f\"train_loss: {loss.item():.4f}\")\n",
    "        # track batch loss metric\n",
    "        aim_run.track(loss.item(), name=\"batch_loss\", context={\"type\": loss_type})\n",
    "\n",
    "    epoch_loss /= step\n",
    "    epoch_loss_values.append(epoch_loss)\n",
    "\n",
    "    # track epoch loss metric\n",
    "    aim_run.track(epoch_loss, name=\"epoch_loss\", context={\"type\": loss_type})\n",
    "\n",
    "    print(f\"epoch {epoch + 1} average loss: {epoch_loss:.4f}\")\n",
    "\n",
    "    if (epoch + 1) % val_interval == 0:\n",
    "        if (epoch + 1) % val_interval * 2 == 0:\n",
    "            # track model params and gradients\n",
    "            track_params_dists(model, aim_run)\n",
    "            # THIS SEGMENT TAKES RELATIVELY LONG (Advise Against it)\n",
    "            track_gradients_dists(model, aim_run)\n",
    "\n",
    "        model.eval()\n",
    "        with torch.no_grad():\n",
    "            for index, val_data in enumerate(val_loader):\n",
    "                val_inputs, val_labels = (\n",
    "                    val_data[\"image\"].to(device),\n",
    "                    val_data[\"label\"].to(device),\n",
    "                )\n",
    "                # roi_size = (160, 160, 160)\n",
    "\n",
    "                sw_batch_size = 4\n",
    "                val_outputs = sliding_window_inference(val_inputs, roi_size, sw_batch_size, model)\n",
    "\n",
    "                # tracking input, label and output images with Aim\n",
    "                output = torch.argmax(val_outputs, dim=1)[0, :, :, slice_to_track].float()\n",
    "\n",
    "                aim_run.track(\n",
    "                    aim.Image(val_inputs[0, 0, :, :, slice_to_track], caption=f\"Input Image: {index}\"),\n",
    "                    name=\"validation\",\n",
    "                    context={\"type\": \"input\"},\n",
    "                )\n",
    "                aim_run.track(\n",
    "                    aim.Image(val_labels[0, 0, :, :, slice_to_track], caption=f\"Label Image: {index}\"),\n",
    "                    name=\"validation\",\n",
    "                    context={\"type\": \"label\"},\n",
    "                )\n",
    "                aim_run.track(\n",
    "                    aim.Image(output, caption=f\"Predicted Label: {index}\"),\n",
    "                    name=\"predictions\",\n",
    "                    context={\"type\": \"labels\"},\n",
    "                )\n",
    "\n",
    "                val_outputs = [post_pred(i) for i in decollate_batch(val_outputs)]\n",
    "                val_labels = [post_label(i) for i in decollate_batch(val_labels)]\n",
    "                # compute metric for current iteration\n",
    "                dice_metric(y_pred=val_outputs, y=val_labels)\n",
    "\n",
    "            # aggregate the final mean dice result\n",
    "            metric = dice_metric.aggregate().item()\n",
    "            # track val metric\n",
    "            aim_run.track(metric, name=\"val_metric\", context={\"type\": loss_type})\n",
    "\n",
    "            # reset the status for next validation round\n",
    "            dice_metric.reset()\n",
    "\n",
    "            metric_values.append(metric)\n",
    "            if metric > best_metric:\n",
    "                best_metric = metric\n",
    "                best_metric_epoch = epoch + 1\n",
    "                torch.save(model.state_dict(), os.path.join(root_dir, f\"{aim_run.name}_best_metric_model.pth\"))\n",
    "\n",
    "                best_model_log_message = f\"saved new best metric model at the {epoch+1}th epoch\"\n",
    "                aim_run.track(aim.Text(best_model_log_message), name=\"best_model_log_message\", epoch=epoch + 1)\n",
    "                print(best_model_log_message)\n",
    "\n",
    "            message1 = f\"current epoch: {epoch + 1} current mean dice: {metric:.4f}\"\n",
    "            message2 = f\"\\nbest mean dice: {best_metric:.4f} \"\n",
    "            message3 = f\"at epoch: {best_metric_epoch}\"\n",
    "\n",
    "            aim_run.track(aim.Text(message1 + \"\\n\" + message2 + message3), name=\"epoch_summary\", epoch=epoch + 1)\n",
    "            print(message1, message2, message3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "d1WMn7DFKkbV"
   },
   "outputs": [],
   "source": [
    "# finalize Aim Run\n",
    "aim_run.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "ygo9hrWswCMR",
    "tags": []
   },
   "outputs": [],
   "source": [
    "print(f\"train completed, best_metric: {best_metric:.4f} \" f\"at epoch: {best_metric_epoch}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "run = aim.Run('dc7f4adf500345a78a890961')\n",
    "run.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "run.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "jGhCvBg-wCMS"
   },
   "outputs": [],
   "source": [
    "%load_ext aim\n",
    "%aim up"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zV7fV0CIwCMS"
   },
   "source": [
    "Once the above cell is executed, you will see the Aim UI running in output cell\n",
    "\n",
    "![Aim UI](https://user-images.githubusercontent.com/13848158/156644374-ba04963f-4f63-4fb9-b3ef-4d4e1ae521cc.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "KRw5pgLiwCMS"
   },
   "source": [
    "## Explore the loss and metric"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "MeX1wBjXwCMS"
   },
   "source": [
    "Compare metrics curves with Metrics Explorer - group and aggregate by any hyperparameter to easily compare training runs\n",
    "\n",
    "![Metrics Explorer](https://user-images.githubusercontent.com/13848158/156642623-8cf4911d-bed2-42b8-9f39-374f8d31def8.jpg)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zGZ5vozGwCMS"
   },
   "source": [
    "## Compare and analyze model outputs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "mZIUa0aNwCMS"
   },
   "source": [
    "Compare models of different runs with input images and labels\n",
    "\n",
    "![Images Explorer](https://user-images.githubusercontent.com/13848158/156642615-c003fb3c-9f37-40f4-b499-ee6623db59ef.jpg)\n",
    "\n",
    "![Images Explorer](https://user-images.githubusercontent.com/13848158/156642618-0c0c380a-75aa-45b1-b431-149f735b3fde.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "uZKhs2DFwCMS"
   },
   "source": [
    "## Evaluation on original image spacings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Ws5wpqPlwCMT"
   },
   "outputs": [],
   "source": [
    "val_org_transforms = Compose(\n",
    "    [\n",
    "        LoadImaged(keys=[\"image\", \"label\"]),\n",
    "        EnsureChannelFirstd(keys=[\"image\", \"label\"]),\n",
    "        Spacingd(keys=[\"image\"], pixdim=(1.5, 1.5, 2.0), mode=\"bilinear\"),\n",
    "        Orientationd(keys=[\"image\"], axcodes=\"RAS\"),\n",
    "        ScaleIntensityRanged(\n",
    "            keys=[\"image\"],\n",
    "            a_min=-57,\n",
    "            a_max=164,\n",
    "            b_min=0.0,\n",
    "            b_max=1.0,\n",
    "            clip=True,\n",
    "        ),\n",
    "        CropForegroundd(keys=[\"image\"], source_key=\"image\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "val_org_ds = Dataset(data=val_files, transform=val_org_transforms)\n",
    "val_org_loader = DataLoader(val_org_ds, batch_size=1, num_workers=4)\n",
    "\n",
    "post_transforms = Compose(\n",
    "    [\n",
    "        Invertd(\n",
    "            keys=\"pred\",\n",
    "            transform=val_org_transforms,\n",
    "            orig_keys=\"image\",\n",
    "            meta_keys=\"pred_meta_dict\",\n",
    "            orig_meta_keys=\"image_meta_dict\",\n",
    "            meta_key_postfix=\"meta_dict\",\n",
    "            nearest_interp=False,\n",
    "            to_tensor=True,\n",
    "        ),\n",
    "        AsDiscreted(keys=\"pred\", argmax=True, to_onehot=2),\n",
    "        AsDiscreted(keys=\"label\", to_onehot=2),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "JTkKUwRGwCMT"
   },
   "outputs": [],
   "source": [
    "model.load_state_dict(torch.load(os.path.join(root_dir, \"best_metric_model.pth\")))\n",
    "model.eval()\n",
    "\n",
    "with torch.no_grad():\n",
    "    for val_data in val_org_loader:\n",
    "        val_data[\"image\"] = val_data[\"image\"].to(device)\n",
    "        val_data[\"label\"] = val_data[\"label\"].to(device)\n",
    "        roi_size = (160, 160, 160)\n",
    "        sw_batch_size = 4\n",
    "        val_data[\"pred\"] = sliding_window_inference(val_data[\"image\"], roi_size, sw_batch_size, model)\n",
    "        val_data = [post_transforms(i) for i in decollate_batch(val_data)]\n",
    "        val_outputs, val_labels = from_engine([\"pred\", \"label\"])(val_data)\n",
    "        # compute metric for current iteration\n",
    "        dice_metric(y_pred=val_outputs, y=val_labels)\n",
    "\n",
    "    # aggregate the final mean dice result\n",
    "    metric_org = dice_metric.aggregate().item()\n",
    "    # reset the status for next validation round\n",
    "    dice_metric.reset()\n",
    "\n",
    "print(\"Metric on original image spacing: \", metric_org)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "chILRaduwCMT"
   },
   "source": [
    "## Cleanup data directory\n",
    "\n",
    "Remove directory if a temporary was used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "yuCFCxOcwCMT"
   },
   "outputs": [],
   "source": [
    "if directory is None:\n",
    "    shutil.rmtree(root_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/mnt/datawow/notebook\r\n"
     ]
    }
   ],
   "source": [
    "!pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "name": "spleen_segmentation_3d_visualization.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  },
  "vscode": {
   "interpreter": {
    "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
